package com.aaron.gesturehelper.gesturelib;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.DataOutputStream;
import java.io.DataInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
import java.util.Map;

/**
 * GestureLibrary maintains gesture examples and makes predictions on a new
 * gesture
 */
//
//    File format for GestureStore:
//
//                Nb. bytes   Java type   Description
//                -----------------------------------
//    Header
//                2 bytes     short       File format version number
//                4 bytes     int         Number of entries
//    Entry
//                X bytes     UTF String  Entry name
//    Gesture
//                8 bytes     long        Gesture ID
//                4 bytes     int         Number of strokes
//    Stroke
//                4 bytes     int         Number of points
//    Point
//                4 bytes     float       X coordinate of the point
//                4 bytes     float       Y coordinate of the point
//                8 bytes     long        Time stamp
//
public class MyGestureStore {

    private static final short FILE_FORMAT_VERSION = 1;

    private final HashMap<String, MyGesture> mNamedGestures =
            new HashMap<String, MyGesture>();

    private MyInstanceLearner mClassifier;

    private boolean mChanged = false;

    public MyGestureStore() {
        mClassifier = new MyInstanceLearner();
    }

    /**
     * Get all the gesture entry names in the library
     * 
     * @return a set of strings
     */
    public Set<String> getGestureEntries() {
        return mNamedGestures.keySet();
    }

    /**
     * Recognize a gesture
     * 
     * @param gesture the query
     * @return a list of predictions of possible entries for a given gesture
     */
    public ArrayList<MyPrediction> recognize(MyGesture gesture) {
        MyInstance instance = MyInstance.createMyInstance(gesture, null);
        return mClassifier.classify(instance);
    }

    /**
     * Add a gesture for the entry
     * 
     * @param entryName entry name
     * @param gesture
     */
    public void addGesture(String entryName, MyGesture gesture) {
        if (entryName == null || entryName.length() == 0) {
            return;
        }
       
        MyGesture old = mNamedGestures.get(entryName);
        if(old != null) {
        	mClassifier.removeInstance(old.getID());
        }
        
        mNamedGestures.put(entryName, gesture);

        mClassifier.addInstance(MyInstance.createMyInstance(gesture, entryName));
        mChanged = true;
    }

    /**
     * Remove a gesture from the library. If there are no more gestures for the
     * given entry, the gesture entry will be removed.
     * 
     * @param entryName entry name
     * @param gesture
     */
    public void removeGesture(String entryName) {
    	MyGesture gesture = mNamedGestures.remove(entryName);
    	if(gesture != null) {
	        mClassifier.removeInstance(gesture.getID());
	
	        mChanged = true;
    	}
    }

    /**
     * Get all the gestures of an entry
     * 
     * @param entryName
     * @return the list of gestures that is under this name
     */
    public MyGesture getGestures(String entryName) {
        return mNamedGestures.get(entryName);
    }

    public boolean hasChanged() {
        return mChanged;
    }

    /**
     * Save the gesture library
     */
    public void save(OutputStream stream) throws IOException {
        save(stream, false);
    }

    public void save(OutputStream stream, boolean closeStream) throws IOException {
        DataOutputStream out = null;

        try {
          
            final HashMap<String, MyGesture> maps = mNamedGestures;

            out = new DataOutputStream((stream instanceof BufferedOutputStream) ? stream :
                    new BufferedOutputStream(stream, 32*1024));
            // Write version number
            out.writeShort(FILE_FORMAT_VERSION);
            // Write number of entries
            out.writeInt(maps.size());

            for (Map.Entry<String, MyGesture> entry : maps.entrySet()) {
                final String key = entry.getKey();
                final MyGesture gesture = entry.getValue();
         
                // Write entry name
                out.writeUTF(key);
                gesture.serialize(out);
            }

            out.flush();

            mChanged = false;
        } finally {
            if (closeStream) MyGestureUtil.closeStream(out);
        }
    }

    /**
     * Load the gesture library
     */
    public void load(InputStream stream) throws IOException {
        load(stream, false);
    }

    public void load(InputStream stream, boolean closeStream) throws IOException {
        DataInputStream in = null;
        try {
            in = new DataInputStream((stream instanceof BufferedInputStream) ? stream :
                    new BufferedInputStream(stream, 32*1024));

            // Read file format version number
            final short versionNumber = in.readShort();
            switch (versionNumber) {
                case 1:
                    readFormatV1(in);
                    break;
            }
        } finally {
            if (closeStream) MyGestureUtil.closeStream(in);
        }
    }

    private void readFormatV1(DataInputStream in) throws IOException {
        final MyInstanceLearner classifier = mClassifier;
        final HashMap<String, MyGesture> namedGestures = mNamedGestures;
        namedGestures.clear();

        // Number of entries in the library
        final int entriesCount = in.readInt();

        for (int i = 0; i < entriesCount; i++) {
            // Entry name
            final String name = in.readUTF();

          
            final MyGesture gesture = MyGesture.deserialize(in);
               
            classifier.addInstance(MyInstance.createMyInstance(gesture, name));
            namedGestures.put(name, gesture);
        }
    }
    
    MyInstanceLearner getLearner() {
        return mClassifier;
    }
}
